home *** CD-ROM | disk | FTP | other *** search
/ The CICA Windows Explosion! / The CICA Windows Explosion! - Disc 2.iso / nt / source.exe / POSIX / ELVIS / EX.C < prev    next >
C/C++ Source or Header  |  1992-09-26  |  16KB  |  676 lines

  1. /* ex.c */
  2.  
  3. /* Author:
  4.  *    Steve Kirkendall
  5.  *    14407 SW Teal Blvd. #C
  6.  *    Beaverton, OR 97005
  7.  *    kirkenda@cs.pdx.edu
  8.  */
  9.  
  10.  
  11. /* This file contains the code for reading ex commands. */
  12.  
  13. #include "config.h"
  14. #include <ctype.h>
  15. #include "vi.h"
  16.  
  17. #ifndef isascii
  18. # define isascii(c) !((c)&~0x7f)
  19. #endif
  20.  
  21. /* This data type is used to describe the possible argument combinations */
  22. typedef short ARGT;
  23. #define FROM    1        /* allow a linespec */
  24. #define    TO    2        /* allow a second linespec */
  25. #define BANG    4        /* allow a ! after the command name */
  26. #define EXTRA    8        /* allow extra args after command name */
  27. #define XFILE    16        /* expand wildcards in extra part */
  28. #define NOSPC    32        /* no spaces allowed in the extra part */
  29. #define    DFLALL    64        /* default file range is 1,$ */
  30. #define DFLNONE    128        /* no default file range */
  31. #define NODFL    256        /* do not default to the current file name */
  32. #define EXRCOK    512        /* can be in a .exrc file */
  33. #define NL    1024        /* if mode!=MODE_EX, then write a newline first */
  34. #define PLUS    2048        /* allow a line number, as in ":e +32 foo" */
  35. #define ZERO    4096        /* allow 0 to be given as a line number */
  36. #define FILES    (XFILE + EXTRA)    /* multiple extra files allowed */
  37. #define WORD1    (EXTRA + NOSPC)    /* one extra word allowed */
  38. #define FILE1    (FILES + NOSPC)    /* 1 file allowed, defaults to current file */
  39. #define NAMEDF    (FILE1 + NODFL)    /* 1 file allowed, defaults to "" */
  40. #define NAMEDFS    (FILES + NODFL)    /* multiple files allowed, default is "" */
  41. #define RANGE    (FROM + TO)    /* range of linespecs allowed */
  42. #define NONE    0        /* no args allowed at all */
  43.  
  44. /* This array maps ex command names to command codes. The order in which
  45.  * command names are listed below is significant -- ambiguous abbreviations
  46.  * are always resolved to be the first possible match.  (e.g. "r" is taken
  47.  * to mean "read", not "rewind", because "read" comes before "rewind")
  48.  */
  49. static struct
  50. {
  51.     char    *name;    /* name of the command */
  52.     CMD    code;    /* enum code of the command */
  53.     void    (*fn)();/* function which executes the command */
  54.     ARGT    argt;    /* command line arguments permitted/needed/used */
  55. }
  56.     cmdnames[] =
  57. {   /*    cmd name    cmd code    function    arguments */
  58.     {"append",    CMD_APPEND,    cmd_append,    FROM+ZERO    },
  59. #ifdef DEBUG
  60.     {"bug",        CMD_DEBUG,    cmd_debug,    RANGE+BANG+EXTRA+NL},
  61. #endif
  62.     {"change",    CMD_CHANGE,    cmd_append,    RANGE        },
  63.     {"delete",    CMD_DELETE,    cmd_delete,    RANGE+WORD1    },
  64.     {"edit",    CMD_EDIT,    cmd_edit,    BANG+FILE1+PLUS    },
  65.     {"file",    CMD_FILE,    cmd_file,    NAMEDF        },
  66.     {"global",    CMD_GLOBAL,    cmd_global,    RANGE+BANG+EXTRA+DFLALL},
  67.     {"insert",    CMD_INSERT,    cmd_append,    FROM        },
  68.     {"join",    CMD_INSERT,    cmd_join,    RANGE        },
  69.     {"k",        CMD_MARK,    cmd_mark,    FROM+WORD1    },
  70.     {"list",    CMD_LIST,    cmd_print,    RANGE+NL    },
  71.     {"move",    CMD_MOVE,    cmd_move,    RANGE+EXTRA    },
  72.     {"next",    CMD_NEXT,    cmd_next,    BANG+NAMEDFS    },
  73.     {"Next",    CMD_PREVIOUS,    cmd_next,    BANG        },
  74.     {"print",    CMD_PRINT,    cmd_print,    RANGE+NL    },
  75.     {"quit",    CMD_QUIT,    cmd_xit,    BANG        },
  76.     {"read",    CMD_READ,    cmd_read,    FROM+ZERO+NAMEDF},
  77.     {"substitute",    CMD_SUBSTITUTE,    cmd_substitute,    RANGE+EXTRA    },
  78.     {"to",        CMD_COPY,    cmd_move,    RANGE+EXTRA    },
  79.     {"undo",    CMD_UNDO,    cmd_undo,    NONE        },
  80.     {"vglobal",    CMD_VGLOBAL,    cmd_global,    RANGE+EXTRA+DFLALL},
  81.     {"write",    CMD_WRITE,    cmd_write,    RANGE+BANG+FILE1+DFLALL},
  82.     {"xit",        CMD_XIT,    cmd_xit,    BANG+NL        },
  83.     {"yank",    CMD_YANK,    cmd_delete,    RANGE+WORD1    },
  84.  
  85.     {"!",        CMD_BANG,    cmd_shell,    EXRCOK+RANGE+NAMEDFS+DFLNONE+NL},
  86.     {"<",        CMD_SHIFTL,    cmd_shift,    RANGE        },
  87.     {">",        CMD_SHIFTR,    cmd_shift,    RANGE        },
  88.     {"=",        CMD_EQUAL,    cmd_file,    RANGE        },
  89.     {"&",        CMD_SUBAGAIN,    cmd_substitute,    RANGE        },
  90. #ifndef NO_AT
  91.     {"@",        CMD_AT,        cmd_at,        EXTRA        },
  92. #endif
  93.  
  94. #ifndef NO_ABBR
  95.     {"abbreviate",    CMD_ABBR,    cmd_abbr,    EXRCOK+EXTRA    },
  96. #endif
  97.     {"args",    CMD_ARGS,    cmd_args,    EXRCOK+NAMEDFS    },
  98. #ifndef NO_ERRLIST
  99.     {"cc",        CMD_CC,        cmd_make,    BANG+FILES    },
  100. #endif
  101.     {"cd",        CMD_CD,        cmd_cd,        EXRCOK+NAMEDF    },
  102.     {"copy",    CMD_COPY,    cmd_move,    RANGE+EXTRA    },
  103. #ifndef NO_DIGRAPH
  104.     {"digraph",    CMD_DIGRAPH,    cmd_digraph,    EXRCOK+BANG+EXTRA},
  105. #endif
  106. #ifndef NO_ERRLIST
  107.     {"errlist",    CMD_ERRLIST,    cmd_errlist,    BANG+NAMEDF    },
  108. #endif
  109.     {"ex",        CMD_EDIT,    cmd_edit,    BANG+FILE1    },
  110.     {"map",        CMD_MAP,    cmd_map,    EXRCOK+BANG+EXTRA},
  111. #ifndef NO_MKEXRC
  112.     {"mkexrc",    CMD_MKEXRC,    cmd_mkexrc,    NAMEDF        },
  113. #endif
  114.     {"number",    CMD_NUMBER,    cmd_print,    RANGE+NL    },
  115.     {"put",        CMD_PUT,    cmd_put,    FROM+ZERO+WORD1    },
  116.     {"set",        CMD_SET,    cmd_set,    EXRCOK+EXTRA    },
  117.     {"shell",    CMD_SHELL,    cmd_shell,    NL        },
  118.     {"source",    CMD_SOURCE,    cmd_source,    EXRCOK+NAMEDF    },
  119.     {"tag",        CMD_TAG,    cmd_tag,    BANG+WORD1    },
  120.     {"version",    CMD_VERSION,    cmd_version,    EXRCOK+NONE    },
  121.     {"visual",    CMD_VISUAL,    cmd_visual,    NONE        },
  122.     {"wq",        CMD_WQUIT,    cmd_xit,    NL        },
  123.  
  124. #ifdef DEBUG
  125.     {"debug",    CMD_DEBUG,    cmd_debug,    RANGE+BANG+EXTRA+NL},
  126.     {"validate",    CMD_VALIDATE,    cmd_validate,    BANG+NL        },
  127. #endif
  128.     {"chdir",    CMD_CD,        cmd_cd,        EXRCOK+NAMEDF    },
  129. #ifndef NO_ERRLIST
  130.     {"make",    CMD_MAKE,    cmd_make,    BANG+NAMEDFS    },
  131. #endif
  132.     {"mark",    CMD_MARK,    cmd_mark,    FROM+WORD1    },
  133.     {"previous",    CMD_PREVIOUS,    cmd_next,    BANG        },
  134.     {"rewind",    CMD_REWIND,    cmd_next,    BANG        },
  135.     {"unmap",    CMD_UNMAP,    cmd_map,    EXRCOK+BANG+EXTRA},
  136. #ifndef NO_ABBR
  137.     {"unabbreviate",CMD_UNABBR,    cmd_abbr,    EXRCOK+WORD1    },
  138. #endif
  139.  
  140.     {(char *)0}
  141. };
  142.  
  143.  
  144. /* This function parses a search pattern - given a pointer to a / or ?,
  145.  * it replaces the ending / or ? with a \0, and returns a pointer to the
  146.  * stuff that came after the pattern.
  147.  */
  148. char    *parseptrn(ptrn)
  149.     REG char    *ptrn;
  150. {
  151.     REG char     *scan;
  152.  
  153.     for (scan = ptrn + 1;
  154.          *scan && *scan != *ptrn;
  155.          scan++)
  156.     {
  157.         /* allow backslashed versions of / and ? in the pattern */
  158.         if (*scan == '\\' && scan[1] != '\0')
  159.         {
  160.             scan++;
  161.         }
  162.     }
  163.     if (*scan)
  164.     {
  165.         *scan++ = '\0';
  166.     }
  167.  
  168.     return scan;
  169. }
  170.  
  171.  
  172. /* This function parses a line specifier for ex commands */
  173. char *linespec(s, markptr)
  174.     REG char    *s;        /* start of the line specifier */
  175.     MARK        *markptr;    /* where to store the mark's value */
  176. {
  177.     long        num;
  178.     REG char    *t;
  179.  
  180.     /* parse each ;-delimited clause of this linespec */
  181.     do
  182.     {
  183.         /* skip an initial ';', if any */
  184.         if (*s == ';')
  185.         {
  186.             s++;
  187.         }
  188.  
  189.         /* skip leading spaces */
  190.         while (isascii(*s) && isspace(*s))
  191.         {
  192.             s++;
  193.         }
  194.     
  195.         /* dot means current position */
  196.         if (*s == '.')
  197.         {
  198.             s++;
  199.             *markptr = cursor;
  200.         }
  201.         /* '$' means the last line */
  202.         else if (*s == '$')
  203.         {
  204.             s++;
  205.             *markptr = MARK_LAST;
  206.         }
  207.         /* digit means an absolute line number */
  208.         else if (isascii(*s) && isdigit(*s))
  209.         {
  210.             for (num = 0; isascii(*s) && isdigit(*s); s++)
  211.             {
  212.                 num = num * 10 + *s - '0';
  213.             }
  214.             *markptr = MARK_AT_LINE(num);
  215.         }
  216.         /* appostrophe means go to a set mark */
  217.         else if (*s == '\'')
  218.         {
  219.             s++;
  220.             *markptr = m_tomark(cursor, 1L, (int)*s);
  221.             s++;
  222.         }
  223.         /* slash means do a search */
  224.         else if (*s == '/' || *s == '?')
  225.         {
  226.             /* put a '\0' at the end of the search pattern */
  227.             t = parseptrn(s);
  228.     
  229.             /* search for the pattern */
  230.             *markptr &= ~(BLKSIZE - 1);
  231.             if (*s == '/')
  232.             {
  233.                 pfetch(markline(*markptr));
  234.                 if (plen > 0)
  235.                     *markptr += plen - 1;
  236.                 *markptr = m_fsrch(*markptr, s);
  237.             }
  238.             else
  239.             {
  240.                 *markptr = m_bsrch(*markptr, s);
  241.             }
  242.     
  243.             /* adjust command string pointer */
  244.             s = t;
  245.         }
  246.     
  247.         /* if linespec was faulty, quit now */
  248.         if (!*markptr)
  249.         {
  250.             return s;
  251.         }
  252.     
  253.         /* maybe add an offset */
  254.         t = s;
  255.         if (*t == '-' || *t == '+')
  256.         {
  257.             s++;
  258.             for (num = 0; *s >= '0' && *s <= '9'; s++)
  259.             {
  260.                 num = num * 10 + *s - '0';
  261.             }
  262.             if (num == 0)
  263.             {
  264.                 num = 1;
  265.             }
  266.             *markptr = m_updnto(*markptr, num, *t);
  267.         }
  268.     } while (*s == ';' || *s == '+' || *s == '-');
  269.  
  270.     return s;
  271. }
  272.  
  273.  
  274.  
  275. /* This function reads an ex command and executes it. */
  276. void ex()
  277. {
  278.     char        cmdbuf[80];
  279.     REG int        cmdlen;
  280.     static long    oldline;
  281.     
  282.     significant = FALSE;
  283.     oldline = markline(cursor);
  284.  
  285.     while (mode == MODE_EX)
  286.     {
  287.         /* read a line */
  288.         cmdlen = vgets(':', cmdbuf, sizeof cmdbuf);
  289.         if (cmdlen < 0)
  290.         {
  291.             return;
  292.         }
  293.  
  294.         /* if empty line, assume ".+1" */
  295.         if (cmdlen == 0)
  296.         {
  297.             strcpy(cmdbuf, ".+1");
  298.             qaddch('\r');
  299.             clrtoeol();
  300.         }
  301.         else
  302.         {
  303.             addch('\n');
  304.         }
  305.         refresh();
  306.  
  307.         /* parse & execute the command */
  308.         doexcmd(cmdbuf);
  309.  
  310.         /* handle autoprint */
  311.         if (significant || markline(cursor) != oldline)
  312.         {
  313.             significant = FALSE;
  314.             oldline = markline(cursor);
  315.             if (*o_autoprint && mode == MODE_EX)
  316.             {
  317.                 cmd_print(cursor, cursor, CMD_PRINT, FALSE, "");
  318.             }
  319.         }
  320.     }
  321. }
  322.  
  323. void doexcmd(cmdbuf)
  324.     char        *cmdbuf;    /* string containing an ex command */
  325. {
  326.     REG char    *scan;        /* used to scan thru cmdbuf */
  327.     MARK        frommark;    /* first linespec */
  328.     MARK        tomark;        /* second linespec */
  329.     REG int        cmdlen;        /* length of the command name given */
  330.     CMD        cmd;        /* what command is this? */
  331.     ARGT        argt;        /* argument types for this command */
  332.     short        forceit;    /* bang version of a command? */
  333.     REG int        cmdidx;        /* index of command */
  334.     REG char    *build;        /* used while copying filenames */
  335.     int        iswild;        /* boolean: filenames use wildcards? */
  336.     int        isdfl;        /* using default line ranges? */
  337.     int        didsub;        /* did we substitute file names for % or # */
  338.  
  339.  
  340.     /* ex commands can't be undone via the shift-U command */
  341.     U_line = 0L;
  342.  
  343.     /* ignore command lines that start with a double-quote */
  344.     if (*cmdbuf == '"')
  345.     {
  346.         return;
  347.     }
  348.  
  349.     /* permit extra colons at the start of the line */
  350.     while (*cmdbuf == ':')
  351.     {
  352.         cmdbuf++;
  353.     }
  354.  
  355.     /* parse the line specifier */
  356.     scan = cmdbuf;
  357.     if (nlines < 1)
  358.     {
  359.         /* no file, so don't allow addresses */
  360.     }
  361.     else if (*scan == '%')
  362.     {
  363.         /* '%' means all lines */
  364.         frommark = MARK_FIRST;
  365.         tomark = MARK_LAST;
  366.         scan++;
  367.     }
  368.     else if (*scan == '0')
  369.     {
  370.         frommark = tomark = MARK_UNSET;
  371.         scan++;
  372.     }
  373.     else
  374.     {
  375.         frommark = cursor;
  376.         scan = linespec(scan, &frommark);
  377.         tomark = frommark;
  378.         if (frommark && *scan == ',')
  379.         {
  380.             scan++;
  381.             scan = linespec(scan, &tomark);
  382.         }
  383.         if (!tomark)
  384.         {
  385.             /* faulty line spec -- fault already described */
  386.             return;
  387.         }
  388.         if (frommark > tomark)
  389.         {
  390.             msg("first address exceeds the second");
  391.             return;
  392.         }
  393.     }
  394.     isdfl = (scan == cmdbuf);
  395.  
  396.     /* skip whitespace */
  397.     while (isascii(*scan) && isspace(*scan))
  398.     {
  399.         scan++;
  400.     }
  401.  
  402.     /* if no command, then just move the cursor to the mark */
  403.     if (!*scan)
  404.     {
  405.         cursor = tomark;
  406.         return;
  407.     }
  408.  
  409.     /* figure out how long the command name is */
  410.     if (isascii(*scan) && !isalpha(*scan))
  411.     {
  412.         cmdlen = 1;
  413.     }
  414.     else
  415.     {
  416.         for (cmdlen = 1;
  417.              !isascii(scan[cmdlen]) || isalpha(scan[cmdlen]);
  418.              cmdlen++)
  419.         {
  420.         }
  421.     }
  422.  
  423.     /* lookup the command code */
  424.     for (cmdidx = 0;
  425.          cmdnames[cmdidx].name && strncmp(scan, cmdnames[cmdidx].name, cmdlen);
  426.          cmdidx++)
  427.     {
  428.     }
  429.     argt = cmdnames[cmdidx].argt;
  430.     cmd = cmdnames[cmdidx].code;
  431.     if (cmd == CMD_NULL)
  432.     {
  433. #if OSK
  434.         msg("Unknown command \"%s\"", scan);
  435. #else
  436.         msg("Unknown command \"%.*s\"", cmdlen, scan);
  437. #endif
  438.         return;
  439.     }
  440.  
  441.     /* if the command ended with a bang, set the forceit flag */
  442.     scan += cmdlen;
  443.     if ((argt & BANG) && *scan == '!')
  444.     {
  445.         scan++;
  446.         forceit = 1;
  447.     }
  448.     else
  449.     {
  450.         forceit = 0;
  451.     }
  452.  
  453.     /* skip any more whitespace, to leave scan pointing to arguments */
  454.     while (isascii(*scan) && isspace(*scan))
  455.     {
  456.         scan++;
  457.     }
  458.  
  459.     /* a couple of special cases for filenames */
  460.     if (argt & XFILE)
  461.     {
  462.         /* if names were given, process them */
  463.         if (*scan)
  464.         {
  465.             for (build = tmpblk.c, iswild = didsub = FALSE; *scan; scan++)
  466.             {
  467.                 switch (*scan)
  468.                 {
  469.                   case '%':
  470.                     if (!*origname)
  471.                     {
  472.                         msg("No filename to substitute for %%");
  473.                         return;
  474.                     }
  475.                     strcpy(build, origname);
  476.                     while (*build)
  477.                     {
  478.                         build++;
  479.                     }
  480.                     didsub = TRUE;
  481.                     break;
  482.     
  483.                   case '#':
  484.                     if (!*prevorig)
  485.                     {
  486.                         msg("No filename to substitute for #");
  487.                         return;
  488.                     }
  489.                     strcpy(build, prevorig);
  490.                     while (*build)
  491.                     {
  492.                         build++;
  493.                     }
  494.                     didsub = TRUE;
  495.                     break;
  496.     
  497.                   case '*':
  498.                   case '?':
  499. #if !(MSDOS || TOS)
  500.                   case '[':
  501.                   case '`':
  502.                   case '{': /* } */
  503.                   case '$':
  504.                   case '~':
  505. #endif
  506.                     *build++ = *scan;
  507.                     iswild = TRUE;
  508.                     break;
  509.  
  510.                   default:
  511.                     *build++ = *scan;
  512.                 }
  513.             }
  514.             *build = '\0';
  515.     
  516.             if (cmd == CMD_BANG
  517.              || cmd == CMD_READ && tmpblk.c[0] == '!'
  518.              || cmd == CMD_WRITE && tmpblk.c[0] == '!')
  519.             {
  520.                 if (didsub)
  521.                 {
  522.                     if (mode != MODE_EX)
  523.                     {
  524.                         addch('\n');
  525.                     }
  526.                     addstr(tmpblk.c);
  527.                     addch('\n');
  528.                     exrefresh();
  529.                 }
  530.             }
  531.             else
  532.             {
  533.                 if (iswild && tmpblk.c[0] != '>')
  534.                 {
  535.                     scan = wildcard(tmpblk.c);
  536.                 }
  537.             }
  538.         }
  539.         else /* no names given, maybe assume origname */
  540.         {
  541.             if (!(argt & NODFL))
  542.             {
  543.                 strcpy(tmpblk.c, origname);
  544.             }
  545.             else
  546.             {
  547.                 *tmpblk.c = '\0';
  548.             }
  549.         }
  550.  
  551.         scan = tmpblk.c;
  552.     }
  553.  
  554.     /* bad arguments? */
  555.     if (!(argt & EXRCOK) && nlines < 1L)
  556.     {
  557.         msg("Can't use the \"%s\" command in a %s file", cmdnames[cmdidx].name, EXRC);
  558.         return;
  559.     }
  560.     if (!(argt & (ZERO | EXRCOK)) && frommark == MARK_UNSET)
  561.     {
  562.         msg("Can't use address 0 with \"%s\" command.", cmdnames[cmdidx].name);
  563.         return;
  564.     }
  565.     if (!(argt & FROM) && frommark != cursor && nlines >= 1L)
  566.     {
  567.         msg("Can't use address with \"%s\" command.", cmdnames[cmdidx].name);
  568.         return;
  569.     }
  570.     if (!(argt & TO) && tomark != frommark && nlines >= 1L)
  571.     {
  572.         msg("Can't use a range with \"%s\" command.", cmdnames[cmdidx].name);
  573.         return;
  574.     }
  575.     if (!(argt & EXTRA) && *scan)
  576.     {
  577.         msg("Extra characters after \"%s\" command.", cmdnames[cmdidx].name);
  578.         return;
  579.     }
  580.     if ((argt & NOSPC) && !(cmd == CMD_READ && (forceit || *scan == '!')))
  581.     {
  582.         build = scan;
  583. #ifndef CRUNCH
  584.         if ((argt & PLUS) && *build == '+')
  585.         {
  586.             while (*build && !(isascii(*build) && isspace(*build)))
  587.             {
  588.                 build++;
  589.             }
  590.             while (*build && isascii(*build) && isspace(*build))
  591.             {
  592.                 build++;
  593.             }
  594.         }
  595. #endif /* not CRUNCH */
  596.         for (; *build; build++)
  597.         {
  598.             if (isspace(*build))
  599.             {
  600.                 msg("Too many %s to \"%s\" command.",
  601.                     (argt & XFILE) ? "filenames" : "arguments",
  602.                     cmdnames[cmdidx].name);
  603.                 return;
  604.             }
  605.         }
  606.     }
  607.  
  608.     /* some commands have special default ranges */
  609.     if (isdfl && (argt & DFLALL))
  610.     {
  611.         frommark = MARK_FIRST;
  612.         tomark = MARK_LAST;
  613.     }
  614.     else if (isdfl && (argt & DFLNONE))
  615.     {
  616.         frommark = tomark = 0L;
  617.     }
  618.  
  619.     /* write a newline if called from visual mode */
  620.     if ((argt & NL) && mode != MODE_EX && !exwrote)
  621.     {
  622.         addch('\n');
  623.         exrefresh();
  624.     }
  625.  
  626.     /* act on the command */
  627.     (*cmdnames[cmdidx].fn)(frommark, tomark, cmd, forceit, scan);
  628. }
  629.  
  630.  
  631. /* This function executes EX commands from a file.  It returns 1 normally, or
  632.  * 0 if the file could not be opened for reading.
  633.  */
  634. int doexrc(filename)
  635.     char    *filename;    /* name of a ".exrc" file */
  636. {
  637.     int    fd;        /* file descriptor */
  638.     int    len;        /* length of the ".exrc" file */
  639.     char    buf[MAXRCLEN];    /* buffer, holds the entire .exrc file */
  640.  
  641.     /* open the file, read it, and close */
  642.     fd = open(filename, O_RDONLY);
  643.     if (fd < 0)
  644.     {
  645.         return 0;
  646.     }
  647.     len = tread(fd, buf, MAXRCLEN);
  648.     close(fd);
  649.  
  650.     /* execute the string */
  651.     exstring(buf, len);
  652.  
  653.     return 1;
  654. }
  655.  
  656. void exstring(buf, len)
  657.     char    *buf;    /* the commands to execute */
  658.     int    len;    /* the length of the string */
  659. {
  660.     char    *cmd;        /* start of a command */
  661.     char    *end;        /* used to search for the end of cmd */
  662.  
  663.     /* find & do each command */
  664.     for (cmd = buf; cmd < &buf[len]; cmd = end + 1)
  665.     {
  666.         /* find the end of the command */
  667.         for (end = cmd; end < &buf[len] && *end != '\n' && *end != '|'; end++)
  668.         {
  669.         }
  670.         *end = '\0';
  671.  
  672.         /* do it */
  673.         doexcmd(cmd);
  674.     }
  675. }
  676.